Skip to content

fix: stop sending fabricated Sec-WebSocket-Protocol and Origin headers on Android - #158

Merged
riteshshukla04 merged 1 commit into
margelo:mainfrom
riteshshukla04:fix/ws-fabricated-handshake-headers
Aug 3, 2026
Merged

fix: stop sending fabricated Sec-WebSocket-Protocol and Origin headers on Android#158
riteshshukla04 merged 1 commit into
margelo:mainfrom
riteshshukla04:fix/ws-fabricated-handshake-headers

Conversation

@riteshshukla04

Copy link
Copy Markdown
Collaborator

Problem

On Android, every WebSocket handshake sent two headers the caller never asked for:

  • Sec-WebSocket-Protocol: nitro-ws when JS requested no subprotocols
  • Origin: http(s)://<host> always

Strict servers (e.g. the WalletConnect relay) reject such handshakes, so pair() hangs forever. Lenient servers ignore the extra headers, which is why nothing else broke. Reported by MetaMask (worked around app-side in MetaMask/metamask-mobile#33871). iOS uses a separate NSURLSession path and never had the bug.

Root cause

lws_client_connect_info.protocol is both the wire Sec-WebSocket-Protocol value and the local callback-binding key unless local_protocol_name is set. The "nitro-ws" fallback existed only to satisfy callback binding and leaked onto the wire. i.origin was set unconditionally, making libwebsockets fabricate an Origin header.

Fix

One site in WebSocketConnection::connect() — covers JS connect, redirect replay, and prewarm:

i.protocol            = protoStr.empty() ? nullptr : protoStr.c_str();
i.local_protocol_name = "nitro-ws";  // binding only, never on wire
// i.origin no longer set

Callers who genuinely need Origin can still pass it via the headers constructor arg (flows through LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER unfiltered).

Also fixes a same-family bug: ws.protocol on Android always returned "nitro-ws" (read from lws_get_protocol, the local entry). The server-selected subprotocol is now read via lws_hdr_copy(WSI_TOKEN_PROTOCOL) in LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH — lws detaches the header table before CLIENT_ESTABLISHED, so that is the last callback where the header is readable.

Tests

New /ws/headers test-server endpoint echoes the received handshake headers as the first message, plus a new "Handshake headers" harness suite:

  1. No protocols → no sec-websocket-protocol, no origin, ws.protocol === '' (the WalletConnect regression)
  2. ['chat', 'superchat'] → offered on the wire, negotiated ws.protocol === 'chat'
  3. Caller-supplied custom headers still sent

Verified on device: full harness suite (9 suites, 254 tests) passes on both Android (Pixel 8 API 35 emulator) and iOS (iPhone 17 Pro simulator).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

@riteshshukla04 is attempting to deploy a commit to the Margelo Team on Vercel.

A member of the Team first needs to authorize it.

@riteshshukla04
riteshshukla04 merged commit a3777ac into margelo:main Aug 3, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant